package drds.configuration.db.xml_mapping.dynamic_tag.tag;

import drds.configuration.db.xml_mapping.configuration.Configuration;
import drds.configuration.db.xml_mapping.dynamic_tag.tag_context.TagContext;
import drds.configuration.db.xml_mapping.dynamic_tag.token_handler.TokenHandler;
import drds.configuration.db.xml_mapping.dynamic_tag.token_handler.TokenParser;
import drds.tools.Constants;
import drds.tools.Uuid;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;

//collection ：数据key
//  　　　　itemAlias ： 表示在迭代过程中每一个元素的别名
//  　　　　open ：前缀
//  　　　　close ：后缀
//  　　　　separator ：分隔符，表示迭代时每个元素之间以什么分隔
public class ForEachTag implements Tag {
    public static final String foreach_item_prefix = "foreach_item";
    private final ExpressionEvaluator expressionEvaluator;
    private final String collectionExpression;
    private final Tag compositeTag;
    private final String open;//前缀
    private final String close;//后缀
    private final String separator;//分隔符，表示迭代时每个元素之间以什么分隔
    private final String itemAlias;//表示在迭代过程中每一个元素的别名
    private final Configuration configuration;
    //
    private final String uuid;

    public ForEachTag(Configuration configuration, Tag compositeTag, String collectionExpression, String itemAlias, @NonNull String open, @NonNull String separator, @NonNull String close) {
        this.expressionEvaluator = new ExpressionEvaluator();
        this.collectionExpression = collectionExpression;
        this.compositeTag = compositeTag;
        this.open = open;
        this.close = close;
        this.separator = separator;
        this.itemAlias = itemAlias;
        this.configuration = configuration;
        //构造动态参数的全局唯一标识
        this.uuid = Uuid.uuid();
    }

    private static String itemizeItem(String item, String uuid, int index) {
        return foreach_item_prefix + "_" + item + "_" + uuid + "_" + index;
    }

    @Override
    public boolean apply(TagContext tagContext) {
        Map<String, Object> tagContextMap = tagContext.getTagContextMap();
        final Iterable<?> iterable = expressionEvaluator.evaluateIterable(collectionExpression, tagContextMap);
        //
        if (!(iterable instanceof List)) {
            throw new IllegalArgumentException("只支持list");
        }
        //
        if (!iterable.iterator().hasNext()) {
            return true;
        }
        boolean first = true;

        applyOpen(tagContext);
        for (Object object : iterable) {
            TagContext forEachLayerTagContext = tagContext;
            if (first) {
                tagContext = new PrefixAppliedTagContext(tagContext, "");
            } else {
                tagContext = new PrefixAppliedTagContext(tagContext, separator);
            }
            int tagContextIndex = tagContext.getTagContextIndex();
            {
                applyItem(uuid, tagContextIndex, tagContext, object);
            }
            compositeTag.apply(new FilteredTagContext(configuration, tagContext, itemAlias, uuid, tagContextIndex));
            if (first) {
                first = !((PrefixAppliedTagContext) tagContext).isPrefixApplied();
            }
            //
            tagContext = forEachLayerTagContext;
        }
        applyClose(tagContext);
        return true;
    }

    private void applyItem(String uuid, int tagContextIndex, TagContext tagContext, Object object) {
        if (itemAlias != null) {
            tagContext.put(itemizeItem(itemAlias, uuid, tagContextIndex), object);
        }
    }

    //
    private void applyOpen(TagContext tagContext) {
        if (open != null) {
            tagContext.sqlAppend(open);
        }
    }

    private void applyClose(TagContext tagContext) {
        if (close != null) {
            tagContext.sqlAppend(close);
        }
    }

    //
    @Slf4j
    private static class FilteredTagContext extends TagContext {
        private final TagContext prefixAppliedTagContext;
        //
        private final String uuid;
        private final String itemAlias;
        private final int tagContextIndex;

        public FilteredTagContext(Configuration configuration, TagContext prefixAppliedTagContext, String itemAlias, String uuid, int tagContextIndex) {
            super(null);
            this.prefixAppliedTagContext = prefixAppliedTagContext;
            //
            this.itemAlias = itemAlias;
            this.uuid = uuid;
            this.tagContextIndex = tagContextIndex;
        }

        @Override
        public Map<String, Object> getTagContextMap() {
            return prefixAppliedTagContext.getTagContextMap();
        }

        @Override
        public void put(String name, Object value) {
            prefixAppliedTagContext.put(name, value);
        }

        @Override
        public String getSql() {
            return prefixAppliedTagContext.getSql();
        }

        @Override
        public void sqlAppend(String sql) {
            TokenParser tokenParser = new TokenParser("#{", "}", new TokenHandler() {
                @Override
                public String handleToken(String content) {
                    if (Constants.developMode) {
                        log.debug("替换前content:" + content);
                    }
                    String newContent = content.replaceFirst("^\\s*" + itemAlias + "(?![^.,:\\s])", itemizeItem(itemAlias, uuid, tagContextIndex));
                    if (Constants.developMode) {
                        log.debug("替换后content:" + newContent);
                    }
                    return "#{" + newContent + "}";
                }
            });

            prefixAppliedTagContext.sqlAppend(tokenParser.parse(sql));
        }

        @Override
        public int getTagContextIndex() {
            return prefixAppliedTagContext.getTagContextIndex();
        }

    }

    //
    private class PrefixAppliedTagContext extends TagContext {
        /**
         * 最顶级TagContext
         */
        private final TagContext forEachLayerTagContext;
        private final String prefix;
        private boolean prefixApplied;

        public PrefixAppliedTagContext(TagContext forEachLayerTagContext, String prefix) {
            super(null);
            this.forEachLayerTagContext = forEachLayerTagContext;
            this.prefix = prefix;
            this.prefixApplied = false;
        }

        public boolean isPrefixApplied() {
            return prefixApplied;
        }

        @Override
        public Map<String, Object> getTagContextMap() {
            return forEachLayerTagContext.getTagContextMap();
        }

        @Override
        public void put(String name, Object value) {
            forEachLayerTagContext.put(name, value);
        }

        @Override
        public void sqlAppend(String sql) {
            if (!prefixApplied && sql != null && sql.trim().length() > 0) {
                forEachLayerTagContext.sqlAppend(prefix);//first prefix then sql
                prefixApplied = true;
            }
            forEachLayerTagContext.sqlAppend(sql);
        }


        @Override
        public String getSql() {
            return forEachLayerTagContext.getSql();
        }

        @Override
        public int getTagContextIndex() {
            return forEachLayerTagContext.getTagContextIndex();
        }
    }


}
